using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Reflection;
using System.Reflection.Emit;
using System.Threading;

public static class AutoIntf
{
	private static readonly Dictionary<Type, Type> interfaceImplementations = new Dictionary<Type, Type>();
	public static object New(Type type)
	{
		if (!interfaceImplementations.ContainsKey(type))
			CreateTypeFor(type);

		return Activator.CreateInstance(interfaceImplementations[type]);
	}
	public static T New<T>() where T : class
	{
		return (T)New(typeof(T));
	}

	static AutoIntf()
	{
		AppDomain appDomain = Thread.GetDomain();
		var assemblyName = new AssemblyName { Name = "InterfaceObjectFactoryAsm" };

		AssemblyBuilder assemblyBuilder = appDomain.DefineDynamicAssembly(
			assemblyName, AssemblyBuilderAccess.RunAndSave);

		// This ModuleBuilder is used for all generated classes.  It's only constructed once, 
		//the first time that the InterfaceObjectFactory is used
		moduleBuilder = assemblyBuilder.DefineDynamicModule(
			"InterfaceObjectFactoryModule", "InterfaceObjectFactory.dll", true);
	}

	/// <summary>
	/// The module builder used for all types constructed
	/// </summary>
	static readonly ModuleBuilder moduleBuilder;

	/// <summary>
	/// Creates a method that will generate an object that implements the interface for the 
	/// given type.
	/// </summary>
	/// <param name="type"></param>
	private static void CreateTypeFor(Type type)
	{
		// Error checking...
		// Make sure that the type is an interface

		if (!type.IsInterface)
			throw new TypeIsNotAnInterface(type);

		TypeBuilder typeBuilder = moduleBuilder.DefineType(
			"ImplOf" + type.Name, TypeAttributes.Class | TypeAttributes.Public | TypeAttributes.BeforeFieldInit | TypeAttributes.AutoLayout, typeof(object), new[] { typeof(INotifyPropertyChanged), type });
		typeBuilder.AddInterfaceImplementation(type);
		typeBuilder.AddInterfaceImplementation(typeof(INotifyPropertyChanged));
		MethodBuilder raiseEvent = GenerateILForINotifyPropertyChanged(typeBuilder);


		// Create Constructor
		ConstructorInfo baseConstructorInfo = typeof(object).GetConstructor(new Type[0]);

		ConstructorBuilder constructorBuilder = typeBuilder.DefineConstructor(
					   MethodAttributes.Public,
					   CallingConventions.Standard,
					   Type.EmptyTypes);

		ILGenerator ilGenerator = constructorBuilder.GetILGenerator();
		ilGenerator.Emit(OpCodes.Ldarg_0);                      // Load "this"
		ilGenerator.Emit(OpCodes.Call, baseConstructorInfo);    // Call the base constructor
		ilGenerator.Emit(OpCodes.Ret);                          // return

		// Get a list of all methods, including methods in inherited interfaces
		// The methods that aren't accessors and will need default implementations...  However,
		// a property's accessors are also methods!
		var methods = new List<MethodInfo>();
		AddMethodsToList(methods, type);

		// Get a list of all of the properties, including properties in inherited interfaces
		var properties = new List<PropertyInfo>();
		AddPropertiesToList(properties, type);

		// Create accessors for each property
		foreach (PropertyInfo pi in properties)
		{
			string piName = pi.Name;
			Type propertyType = pi.PropertyType;

			// Create underlying field; all properties have a field of the same type
			FieldBuilder field = typeBuilder.DefineField("_" + piName, propertyType, FieldAttributes.Private);

			// If there is a getter in the interface, create a getter in the new type
			MethodInfo getMethod = pi.GetGetMethod();
			MethodBuilder getterBuilder = null;
			if (null != getMethod)
			{
				// This will prevent us from creating a default method for the property's getter
				methods.Remove(getMethod);

				// Now we will generate the getter method
				getterBuilder = typeBuilder.DefineMethod(
					getMethod.Name,
					MethodAttributes.Public | MethodAttributes.Virtual,
					propertyType,
					Type.EmptyTypes);

				// The ILGenerator class is used to put op-codes (similar to assembly) into the method
				ilGenerator = getterBuilder.GetILGenerator();

				// These are the op-codes, (similar to assembly)
				ilGenerator.Emit(OpCodes.Ldarg_0);      // Load "this"
				ilGenerator.Emit(OpCodes.Ldfld, field); // Load the property's underlying field onto the stack
				ilGenerator.Emit(OpCodes.Ret);          // Return the value on the stack

				// We need to associate our new type's method with the getter method in the 
				// interface
				typeBuilder.DefineMethodOverride(getterBuilder, getMethod);
			}

			// If there is a setter in the interface, create a setter in the new type
			MethodInfo setMethod = pi.GetSetMethod();
			MethodBuilder setterBuilder = null;
			if (null != setMethod)
			{
				// This will prevent us from creating a default method for the property's 
				// setter
				methods.Remove(setMethod);

				// Now we will generate the setter method
				setterBuilder = typeBuilder.DefineMethod(
					setMethod.Name,
					MethodAttributes.Public | MethodAttributes.Virtual,
					typeof(void),
					new[] { pi.PropertyType });

				// The ILGenerator class is used to put op-codes (similar to assembly) into the
				// method
				ilGenerator = setterBuilder.GetILGenerator();

				// These are the op-codes, (similar to assembly)
				ilGenerator.Emit(OpCodes.Ldarg_0);      // Load "this"
				ilGenerator.Emit(OpCodes.Ldarg_1);      // Load "value" onto the stack
				ilGenerator.Emit(OpCodes.Stfld, field); // Set the field equal to the "value" 
				// on the stack

				// raise event
				ilGenerator.Emit(OpCodes.Ldarg_0);
				ilGenerator.Emit(OpCodes.Ldstr, pi.Name);
				ilGenerator.Emit(OpCodes.Call, raiseEvent);

				ilGenerator.Emit(OpCodes.Ret);          // Return nothing



				// We need to associate our new type's method with the setter method in the 
				// interface
				typeBuilder.DefineMethodOverride(setterBuilder, setMethod);
			}
			PropertyBuilder prop = typeBuilder.DefineProperty(piName, PropertyAttributes.None, propertyType, null);
			if (getterBuilder != null)
				prop.SetGetMethod(getterBuilder);
			if (setterBuilder != null)
				prop.SetSetMethod(setterBuilder);
		}

		// Create default methods.  These methods will essentially be no-ops; if there is a 
		// return value, they will either return a default value or null
		foreach (MethodInfo methodInfo in methods)
		{
			// Get the return type and argument types

			Type returnType = methodInfo.ReturnType;

			var argumentTypes = new List<Type>();
			foreach (ParameterInfo parameterInfo in methodInfo.GetParameters())
				argumentTypes.Add(parameterInfo.ParameterType);

			// Define the method
			MethodBuilder methodBuilder = typeBuilder.DefineMethod(
				methodInfo.Name,
				MethodAttributes.Public | MethodAttributes.Virtual,
				returnType,
				argumentTypes.ToArray());

			// The ILGenerator class is used to put op-codes (similar to assembly) into the
			// method
			ilGenerator = methodBuilder.GetILGenerator();

			// If there's a return type, create a default value or null to return
			if (returnType != typeof(void))
			{
				LocalBuilder localBuilder =
					ilGenerator.DeclareLocal(returnType);   // this declares the local object, 
				// int, long, float, ect
				ilGenerator.Emit(
					OpCodes.Ldloc, localBuilder);           // load the value on the stack to 
				// return
			}

			ilGenerator.Emit(OpCodes.Ret);                  // return

			// We need to associate our new type's method with the method in the interface
			typeBuilder.DefineMethodOverride(methodBuilder, methodInfo);
		}

		// Finally, after all the fields and methods are generated, create the type for use at
		// run-time
		Type createdType = typeBuilder.CreateType();
		interfaceImplementations[type] = createdType;
	}

	/// <summary>
	/// Helper method to get all MethodInfo objects from an interface.  This recurses to all 
	/// sub-interfaces
	/// </summary>
	/// <param name="methods"></param>
	/// <param name="type"></param>
	private static void AddMethodsToList(List<MethodInfo> methods, Type type)
	{
		methods.AddRange(type.GetMethods());

		foreach (Type subInterface in type.GetInterfaces())
			AddMethodsToList(methods, subInterface);
	}

	/// <summary>
	/// Helper method to get all PropertyInfo objects from an interface.  This recurses to all 
	/// sub-interfaces
	/// </summary>
	/// <param name="properties"></param>
	/// <param name="type"></param>
	private static void AddPropertiesToList(List<PropertyInfo> properties, Type type)
	{
		properties.AddRange(type.GetProperties());

		foreach (Type subInterface in type.GetInterfaces())
			AddPropertiesToList(properties, subInterface);
	}

	/// <summary>
	/// Thrown when an attempt is made to create an object of a type that is not an interface
	/// </summary>
	public class TypeIsNotAnInterface : Exception
	{
		internal TypeIsNotAnInterface(Type type)
			: base("The InterfaceObjectFactory only works with interfaces.  "
				+ "An attempt was made to create an object for the following type, "
				+ "which is not an interface: " + type.FullName)
		{ }
	}

	private static MethodBuilder GenerateILForINotifyPropertyChanged(TypeBuilder typeBuilder)
	{
		MethodInfo delegateCombine = typeof(Delegate).GetMethod("Combine", new[] { typeof(Delegate), typeof(Delegate) });
		MethodInfo delegateRemove = typeof(Delegate).GetMethod("Remove", new[] { typeof(Delegate), typeof(Delegate) });
		MethodInfo invokeDelegate = typeof(PropertyChangedEventHandler).GetMethod("Invoke");
		FieldBuilder eventBack = typeBuilder.DefineField("PropertyChanged", typeof(PropertyChangedEventHandler), FieldAttributes.Private);
		ConstructorInfo createEventArgs = typeof(PropertyChangedEventArgs).GetConstructor(new[] { typeof(String) });


		MethodBuilder addPropertyChanged = typeBuilder.DefineMethod(
			"add_PropertyChanged", MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.SpecialName | MethodAttributes.Final | MethodAttributes.HideBySig | MethodAttributes.NewSlot,
			typeof(void), new[] { typeof(PropertyChangedEventHandler) });
		ILGenerator gen = addPropertyChanged.GetILGenerator();
		gen.Emit(OpCodes.Ldarg_0);
		gen.Emit(OpCodes.Ldarg_0);
		gen.Emit(OpCodes.Ldfld, eventBack);
		gen.Emit(OpCodes.Ldarg_1);
		gen.Emit(OpCodes.Call, delegateCombine);
		gen.Emit(OpCodes.Castclass, typeof(PropertyChangedEventHandler));
		gen.Emit(OpCodes.Stfld, eventBack);
		gen.Emit(OpCodes.Ret);

		MethodBuilder removePropertyChanged = typeBuilder.DefineMethod(
			"remove_PropertyChanged", MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.SpecialName | MethodAttributes.Final | MethodAttributes.HideBySig | MethodAttributes.NewSlot,
			typeof(void), new[] { typeof(PropertyChangedEventHandler) });
		gen = removePropertyChanged.GetILGenerator();
		gen.Emit(OpCodes.Ldarg_0);
		gen.Emit(OpCodes.Ldarg_0);
		gen.Emit(OpCodes.Ldfld, eventBack);
		gen.Emit(OpCodes.Ldarg_1);
		gen.Emit(OpCodes.Call, delegateRemove);
		gen.Emit(OpCodes.Castclass, typeof(PropertyChangedEventHandler));
		gen.Emit(OpCodes.Stfld, eventBack);
		gen.Emit(OpCodes.Ret);

		MethodBuilder raisePropertyChanged = typeBuilder.DefineMethod(
			"OnPropertyChanged", MethodAttributes.Public,
			typeof(void), new[] { typeof(String) });
		gen = raisePropertyChanged.GetILGenerator();
		Label lblDelegateOk = gen.DefineLabel();
		gen.DeclareLocal(typeof(PropertyChangedEventHandler));
		gen.Emit(OpCodes.Nop);
		gen.Emit(OpCodes.Ldarg_0);
		gen.Emit(OpCodes.Ldfld, eventBack);
		gen.Emit(OpCodes.Stloc_0);
		gen.Emit(OpCodes.Ldloc_0);
		gen.Emit(OpCodes.Ldnull);
		gen.Emit(OpCodes.Ceq);
		gen.Emit(OpCodes.Brtrue, lblDelegateOk);
		gen.Emit(OpCodes.Ldloc_0);
		gen.Emit(OpCodes.Ldarg_0);
		gen.Emit(OpCodes.Ldarg_1);
		gen.Emit(OpCodes.Newobj, createEventArgs);
		gen.Emit(OpCodes.Callvirt, invokeDelegate);
		gen.MarkLabel(lblDelegateOk);
		gen.Emit(OpCodes.Ret);

		EventBuilder pcevent = typeBuilder.DefineEvent("PropertyChanged", EventAttributes.None, typeof(PropertyChangedEventHandler));
		pcevent.SetRaiseMethod(raisePropertyChanged);
		pcevent.SetAddOnMethod(addPropertyChanged);
		pcevent.SetRemoveOnMethod(removePropertyChanged);

		return raisePropertyChanged;
	}

	public static void Copy(object dst, object src)
	{
		if (dst.GetType() != src.GetType())
			throw new Exception(string.Format("Type mismatch: {0} <= {1}", dst.GetType().Name, src.GetType().Name));
		foreach (var prop in dst.GetType().GetProperties())
		{
			prop.SetValue(dst, prop.GetValue(src, null), null);
		}
	}
}

